package se.jocke.nb.log.internal;

import com.google.common.util.concurrent.RateLimiter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import se.jocke.nb.log.TailListener;

/**
 *
 * @author jocke
 */
public class Tailer implements Runnable {

    private volatile boolean running = false;

    private final File file;

    private final RandomAccessFile randomAccessFile;

    public static final int BUFFER_SIZE = 64;

    private final byte[] buffer;

    private final TailListener listener;

    public Tailer(File file, int fromLine, TailListener listener) throws FileNotFoundException, IOException {
        this.file = file;
        this.randomAccessFile = new RandomAccessFile(file, "r");
        this.buffer = new byte[BUFFER_SIZE];
        this.listener = listener;
        seekToLineFromEnd(fromLine);
    }

    public boolean isRunning() {
        return running;
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    public void close() throws IOException {
        this.running = false;
    }

    private void seekToLineFromEnd(int fromLine) throws IOException {
        if (this.file.length() > 100) {

            int lines = 0;
            long seek = this.file.length();

            while (lines < fromLine && seek >= 2) {
                seek = (seek - 2);
                randomAccessFile.seek(seek);
                byte readByte = randomAccessFile.readByte();
                if (readByte == '\n') {
                    lines++;
                }
            }

        }
    }

    @Override
    public void run() {

        RateLimiter limiter = RateLimiter.create(5);

        long lastModified = 0;

        try {

            while (isRunning()) {
                try {
                    limiter.acquire();
                    long mod = file.lastModified();

                    if (mod > lastModified) {
                        lastModified = mod;
                        readLines();
                    }

                } catch (IOException ex) {
                    listener.onIOException(ex);
                }
            }

        } finally {
            try {
                randomAccessFile.close();
            } catch (IOException ex) {
            }
        }

    }

    /**
     * @See org.apache.commons.io.input.Tailer
     */
    private long readLines() throws IOException {
        Charset cset = Charset.defaultCharset();
        ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(64);
        long pos = randomAccessFile.getFilePointer();
        long rePos = pos;
        int num;
        boolean seenCR = false;
        while (isRunning() && ((num = randomAccessFile.read(buffer)) != -1)) {
            for (int i = 0; i < num; i++) {
                final byte ch = buffer[i];
                switch (ch) {
                    case '\n':
                        seenCR = false;
                        listener.onLine(new String(lineBuf.toByteArray(), cset));
                        lineBuf.reset();
                        rePos = pos + i + 1;
                        break;
                    case '\r':
                        if (seenCR) {
                            lineBuf.write('\r');
                        }
                        seenCR = true;
                        break;
                    default:
                        if (seenCR) {
                            seenCR = false;
                            listener.onLine(new String(lineBuf.toByteArray(), cset));
                            lineBuf.reset();
                            rePos = pos + i + 1;
                        }
                        lineBuf.write(ch);
                }
            }
            pos = randomAccessFile.getFilePointer();
        }
        randomAccessFile.seek(rePos);
        return rePos;
    }

}
